// Corecalc, a spreadsheet core implementation
// ----------------------------------------------------------------------
// Copyright (c) 2006-2013 Peter Sestoft and others

// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

//  * The above copyright notice and this permission notice shall be
//    included in all copies or substantial portions of the Software.

//  * The software is provided "as is", without warranty of any kind,
//    express or implied, including but not limited to the warranties of
//    merchantability, fitness for a particular purpose and
//    noninfringement.  In no event shall the authors or copyright
//    holders be liable for any claim, damages or other liability,
//    whether in an action of contract, tort or otherwise, arising from,
//    out of or in connection with the software or the use or other
//    dealings in the software.
// ----------------------------------------------------------------------

using System;
using System.Reflection;
using System.Reflection.Emit;             // LocalBuilder etc
using System.Text;
using System.Diagnostics;
using System.Collections.Generic;
using Corecalc.Funcalc;                   // For SdfInfo
using Sisal;

// Class Value and its subclasses are used to represent the possible 
// values of a spreadsheet cell    

namespace Corecalc {

  // A Value is the result of evaluating an expression

  public abstract class Value : IEquatable<Value> {
    public static readonly Type type = typeof(Value);
    public static readonly MethodInfo toDoubleOrNanMethod
      = type.GetMethod("ToDoubleOrNan", new Type[] { type });

    public abstract int CommunicationCost { get; }

    public virtual void Apply(Action<Value> act) {
      act(this);
    }

    public abstract bool Equals(Value v);

    // Called from interpreted EXTERN and from generated bytecode
    public static Object ToObject(Value v) {
      return v.ToObject();
    }

    public static double ToDoubleOrNan(Value v) {
      if (v is NumberValue)
        return (v as NumberValue).value;
      else if (v is ErrorValue)
        return (v as ErrorValue).ErrorNan;
      else
        return ErrorValue.argTypeError.ErrorNan;
    }

    // For external methods that do not return anything -- or make it null?
    public static Value MakeVoid(Object o /* ignored */) {
      return TextValue.VOID;
    }

    public abstract Object ToObject();
  }

  // ----------------------------------------------------------------
  // A NumberValue is a floating-point number

  public class NumberValue : Value {
    public readonly double value;

    public override int CommunicationCost {
        get { return CommunicationCosts.Get("double"); }
    }

    public static readonly NumberValue
      ZERO = new NumberValue(0.0),
      ONE = new NumberValue(1.0),
      PI = new NumberValue(Math.PI);

    public new static readonly Type type = typeof(NumberValue);
    public static readonly FieldInfo
      zeroField = type.GetField("ZERO"),
      oneField = type.GetField("ONE"),
      piField = type.GetField("PI");
    public static readonly MethodInfo
      makeMethod = type.GetMethod("Make", new Type[] { typeof(double) });

    private NumberValue(double value) {
      Debug.Assert(!Double.IsInfinity(value) && !Double.IsNaN(value));
      this.value = value;
    }

    public static Value Make(double d) {
      if (double.IsInfinity(d))
        return ErrorValue.numError;
      else if (double.IsNaN(d))
        return ErrorValue.FromNan(d);
      else if (d == 0)
        return ZERO;
      else if (d == 1)
        return ONE;
      else
        return new NumberValue(d);
    }

    public override bool Equals(Value v) {
      return v is NumberValue && (v as NumberValue).value == value;
    }

    public override int GetHashCode() {
      return value.GetHashCode();
    }

    public override Object ToObject() {
      return (Object)value;
    }

    public static Object ToDouble(Value v) {
      NumberValue nv = v as NumberValue;
      return nv != null ? (Object)nv.value : null;	// Causes boxing
    }

    public static Value FromDouble(Object o) {
      if (o is double)
        return Make((double)o);
      else
        return ErrorValue.numError;
    }

    public static Object ToSingle(Value v) {
      NumberValue nv = v as NumberValue;
      return nv != null ? (Object)(float)nv.value : null;	// Causes boxing
    }

    public static Value FromSingle(Object o) {
      if (o is float)
        return Make((float)o);
      else
        return ErrorValue.numError;
    }

    public static Object ToInt64(Value v) {
      NumberValue nv = v as NumberValue;
      return nv != null ? (Object)(long)nv.value : null; // Causes boxing
    }

    public static Value FromInt64(Object o) {
      if (o is long)
        return Make((long)o);
      else
        return ErrorValue.numError;
    }

    public static Object ToInt32(Value v) {
      NumberValue nv = v as NumberValue;
      return nv != null ? (Object)(int)nv.value : null; // Causes boxing
    }

    public static Value FromInt32(Object o) {
      if (o is int)
        return Make((int)o);
      else
        return ErrorValue.numError;
    }

    public static Object ToInt16(Value v) {
      NumberValue nv = v as NumberValue;
      return nv != null ? (Object)(short)nv.value : null; // Causes boxing
    }

    public static Value FromInt16(Object o) {
      if (o is short)
        return Make((short)o);
      else
        return ErrorValue.numError;
    }

    public static Object ToSByte(Value v) {
      NumberValue nv = v as NumberValue;
      return nv != null ? (Object)(sbyte)nv.value : null; // Causes boxing
    }

    public static Value FromSByte(Object o) {
      if (o is sbyte)
        return Make((sbyte)o);
      else
        return ErrorValue.numError;
    }

    public static Object ToUInt64(Value v) {
      NumberValue nv = v as NumberValue;
      return nv != null ? (Object)(ulong)nv.value : null; // Causes boxing
    }

    public static Value FromUInt64(Object o) {
      if (o is ulong)
        return Make((ulong)o);
      else
        return ErrorValue.numError;
    }

    public static Object ToUInt32(Value v) {
      NumberValue nv = v as NumberValue;
      return nv != null ? (Object)(uint)nv.value : null; // Causes boxing
    }

    public static Value FromUInt32(Object o) {
      if (o is uint)
        return Make((uint)o);
      else
        return ErrorValue.numError;
    }

    public static Object ToUInt16(Value v) {
      NumberValue nv = v as NumberValue;
      return nv != null ? (Object)(ushort)nv.value : null; // Causes boxing
    }

    public static Value FromUInt16(Object o) {
      if (o is ushort)
        return Make((ushort)o);
      else
        return ErrorValue.numError;
    }

    public static Object ToByte(Value v) {
      NumberValue nv = v as NumberValue;
      return nv != null ? (Object)(byte)nv.value : null; // Causes boxing
    }

    public static Value FromByte(Object o) {
      if (o is byte)
        return Make((byte)o);
      else
        return ErrorValue.numError;
    }

    public static Object ToBoolean(Value v) {
      NumberValue nv = v as NumberValue;
      return nv != null ? (Object)(nv.value != 0) : null;	// Causes boxing
    }

    public static Value FromBoolean(Object o) {
      if (o is bool)
        return (bool)o ? ONE : ZERO;
      else
        return ErrorValue.numError;
    }

    // Conversion between System.DateTime ticks and Excel-style date numbers
    private static readonly long basedate = new DateTime(1899, 12, 30).Ticks;
    private static readonly double daysPerTick = 100E-9 / 60 / 60 / 24;

    public static double DoubleFromDateTimeTicks(long ticks) {
      return (ticks - basedate) * daysPerTick;
    }

    public override String ToString() {
      return value.ToString();
    }
  }

  // ----------------------------------------------------------------
  // A TextValue contains a string

  public class TextValue : Value {
    public readonly String value;  // Non-null

    public override int CommunicationCost {
        get { return CommunicationCosts.Get("char") * value.Length; }
    }

    public new static readonly Type type = typeof(TextValue);
    public static readonly FieldInfo
      valueField = type.GetField("value"),
      emptyField = type.GetField("EMPTY"),
      voidField = type.GetField("VOID");
    public static readonly MethodInfo
      fromIndexMethod = type.GetMethod("FromIndex"),
      fromNakedCharMethod = type.GetMethod("FromNakedChar"),
      toNakedCharMethod = type.GetMethod("ToNakedChar");

    // Caching TextValues by string contents, and for access by integer index
    private static ValueCache<String, TextValue> textValueCache
      = new ValueCache<String, TextValue>((index, s) => new TextValue(s));

    public static readonly TextValue
      EMPTY = MakeInterned(String.Empty),
      VOID = MakeInterned("<void>");

    private TextValue(String value) {
      this.value = value;
    }

    public static int GetIndex(String s) {
      return textValueCache.GetIndex(s);
    }

    public static TextValue MakeInterned(String s) {
      return textValueCache[textValueCache.GetIndex(s)];
    }

    public static TextValue Make(string s) {
      Debug.Assert(s != null);  // Else use the defensive FromString
      if (s == "")
        return TextValue.EMPTY;
      else
        return new TextValue(s);  // On purpose NOT interned!
    }

    // These five are called also from generated code:

    public static TextValue FromIndex(int index) {
      return textValueCache[index];
    }

    public static Value FromString(Object o) {
      if (o is String)
        return Make(o as String);
      else
        return ErrorValue.argTypeError;
    }

    public static String ToString(Value v) {
      TextValue tv = v as TextValue;
      return tv != null ? tv.value : null;
    }

    public static Value FromNakedChar(char c) {
      return Make(c.ToString());
    }

    public static char ToNakedChar(TextValue v) {
      return v.value.Length >= 1 ? v.value[0] : '\0';
    }

    public static Value FromChar(Object o) {
      if (o is char)
        return Make(((char)o).ToString());
      else
        return ErrorValue.argTypeError;
    }

    public static Object ToChar(Value v) {
      TextValue tv = v as TextValue;
      return tv != null && tv.value.Length >= 1 ? (Object)tv.value[0] : null; // causes boxing
    }

    public override bool Equals(Value v) {
      return v is TextValue && (v as TextValue).value.Equals(value);
    }

    public override int GetHashCode() {
      return value.GetHashCode();
    }

    public override Object ToObject() {
      return (Object)value;
    }

    public override String ToString() {
      return value;
    }
  }

  // ----------------------------------------------------------------
  // An ObjectValue holds a .NET object; may be null

  public class ObjectValue : Value {
    public readonly Object value;
    public static readonly ObjectValue nullObjectValue = new ObjectValue(null);

    public override int CommunicationCost {
        get { throw new System.NotImplementedException(); }
    }

    public ObjectValue(Object value) {
      this.value = value;
    }

    // Used from EXTERN and from generated bytecode
    public static Value Make(Object o) {
      if (o == null)
        return nullObjectValue;
      else
        return new ObjectValue(o);
    }

    public override bool Equals(Value v) {
      return v is ObjectValue && (v as ObjectValue).value.Equals(value);
    }

    public override Object ToObject() {
      return value;
    }

    public override String ToString() {
      return value == null ? "null" : value.ToString();
    }
  }

  // ----------------------------------------------------------------
  // An ErrorValue is a value indicating failure of evaluation

  public class ErrorValue : Value {
    public readonly String message;
    public readonly int index;

    public override int CommunicationCost {
        get { throw new System.NotImplementedException(); }
    }

    // Standard ErrorValue objects and static fields, shared between all functions:
    public new static readonly Type type = typeof(ErrorValue);

    public static readonly MethodInfo
      fromNanMethod = type.GetMethod("FromNan"),
      fromIndexMethod = type.GetMethod("FromIndex");

    // Caching ErrorValues by message string, and for access by integer index
    private static readonly ValueCache<String, ErrorValue> errorTable
      = new ValueCache<string, ErrorValue>((index, message) => new ErrorValue(message, index));

    public static readonly ErrorValue
      // The numError is first so it gets indedunx zero; necessary because
      // System.Math functions produce NaN with error code zero:
      numError =         Make("#NUM!"),
      argCountError =    Make("#ERR: ArgCount"),
      argTypeError =     Make("#ERR: ArgType"),
      nameError =        Make("#NAME?"),
      refError =         Make("#REF!"),
      valueError =       Make("#VALUE!"),
      naError =          Make("#NA"),
      tooManyArgsError = Make("#ERR: Too many arguments");

    private ErrorValue(String message, int errorIndex) {
      this.message = message;
      this.index = errorIndex;
    }

    public static int GetIndex(String message) {
      return errorTable.GetIndex(message);
    }

    public static ErrorValue Make(String message) {
      return errorTable[errorTable.GetIndex(message)];
    }

    public double ErrorNan {
      get { return MakeNan(index); }
    }

    // These two are also called from compiled code, through reflection:

    public static ErrorValue FromNan(double d) {
      return errorTable[ErrorCode(d)];
    }

    public static ErrorValue FromIndex(int errorIndex) {
      return errorTable[errorIndex];
    }

    public override bool Equals(Value v) {
      return v is ErrorValue && (v as ErrorValue).index == index;
    }

    public override int GetHashCode() {
      return index;
    }

    public override Object ToObject() {
      return (Object)this;
    }

    public override String ToString() {
      return message;
    }

    // From error code index (int) to NaN (double) and back

    public static double MakeNan(int errorIndex) {
      long nanbits = System.BitConverter.DoubleToInt64Bits(Double.NaN);
      return System.BitConverter.Int64BitsToDouble(nanbits | (uint)errorIndex);
    }

    public static int ErrorCode(double d) {
      return (int)System.BitConverter.DoubleToInt64Bits(d);
    }
  }

  // ----------------------------------------------------------------
  // An ArrayValue is a rectangle of Values 

  public abstract class ArrayValue : Value {
    public new static readonly Type type = typeof(ArrayValue);

    public abstract int Cols { get; }

    public abstract int Rows { get; }

    public override int CommunicationCost {
        get { throw new System.NotImplementedException(); }
    }

    // Get cell value from array value
    public virtual Value this[CellAddr ca] {
      get { return this[ca.col, ca.row]; }
    }

    public abstract Value this[int col, int row] { get; }

    public override Object ToObject() {
      return (Object)this;
    }

    // Used to implement INDEX(area,row,col), truncated 1-based indexing
    public Value Index(double deltaRow, double deltaCol) {
      int col = (int)deltaCol - 1, row = (int)deltaRow - 1;
      if (0 <= col && col < Cols && 0 <= row && row < Rows)
        return this[col, row] ?? NumberValue.ZERO;
      else
        return ErrorValue.refError;
    }

    public abstract Value View(CellAddr ulCa, CellAddr lrCa);

    public abstract Value Slice(CellAddr ulCa, CellAddr lrCa);

    // Used to implement SLICE(arr, r1, c1, r2, c2)
    // A slice may be empty (when r1=r2+1 or c1=c2+1) whereas a View usually is not
    public Value Slice(double r1, double c1, double r2, double c2) {
      int ir1 = (int)r1 - 1, ic1 = (int)c1 - 1, ir2 = (int)r2 - 1, ic2 = (int)c2 - 1;
      if (0 <= ir1 && ir1 <= ir2 + 1 && ir2 < Rows && 0 <= ic1 && ic1 <= ic2 + 1 && ic2 < Cols)
        return Slice(new CellAddr(ic1, ir1), new CellAddr(ic2, ir2));
      else
        return ErrorValue.refError;
    }

    // These are called from interpreted EXTERN and from generated bytecode
    public static double[] ToDoubleArray1D(Value v) {
      ArrayValue arr = v as ArrayValue;
      if (arr != null && arr.Rows == 1) {
        double[] res = new double[arr.Cols];
        for (int c = 0; c < arr.Cols; c++)
            if (arr[c, 0] is NumberValue)
              res[c] = (arr[c, 0] as NumberValue).value;
            else
              return null;
        return res;
      } else
        return null;
    }

    public static double[,] ToDoubleArray2D(Value v) {
      ArrayValue arr = v as ArrayValue;
      if (arr != null) 
        return arr.ToDoubleArray2DFast();
      else
        return null;
    }

    public static String[] ToStringArray1D(Value v) {
      ArrayValue arr = v as ArrayValue;
      if (arr != null && arr.Rows == 1) {
        String[] res = new String[arr.Cols];
        for (int c = 0; c < arr.Cols; c++)
            if (arr[c, 0] is TextValue)
              res[c] = (arr[c, 0] as TextValue).value;
            else
              return null;
        return res;
      } else
        return null;
    }

    public static Value FromStringArray1D(Object o) {
      String[] ss = o as String[];
      if (ss != null) {
        Value[,] vs = new Value[ss.Length, 1];
        for (int c = 0; c < ss.Length; c++)
          vs[c, 0] = TextValue.FromString(ss[c]);
        return new ArrayExplicit(vs);
      } else
        return ErrorValue.argTypeError;
    }

    public static Value FromDoubleArray1D(Object o) {
      double[] xs = o as double[];
      if (xs != null) {
        Value[,] vs = new Value[xs.Length, 1];
        for (int c = 0; c < xs.Length; c++)
          vs[c, 0] = NumberValue.Make(xs[c]);
        return new ArrayExplicit(vs);
      } else
        return ErrorValue.argTypeError;
    }

    public static Value FromDoubleArray2D(Object o) {
      double[,] xs = o as double[,];
      if (xs != null)
        return new ArrayDouble(xs);
      else
        return ErrorValue.argTypeError;
    }
      
    // Override in ArrayDouble for efficiency
    public virtual double[,] ToDoubleArray2DFast() {
      double[,] res = new double[Rows, Cols];
      for (int c = 0; c < Cols; c++)
        for (int r = 0; r < Rows; r++)
          if (this[c, r] is NumberValue)
            res[r, c] = (this[c, r] as NumberValue).value;
          else
            return null;
      return res;
    }

    public void Apply(Action<double> act) {
      for (int c = 0; c < Cols; c++) {
        for (int r = 0; r < Rows; r++) {
          Value v = this[c, r];
          if (v != null) // Only non-blank cells contribute
            if (v is ArrayValue)
              (v as ArrayValue).Apply(act);
            else
              act(Value.ToDoubleOrNan(v));
        }
      }
    }

    public override void Apply(Action<Value> act) {
      for (int c = 0; c < Cols; c++) {
        for (int r = 0; r < Rows; r++) {
          Value v = this[c, r];
          if (v != null) // Only non-blank cells contribute
            if (v is ArrayValue)
              (v as ArrayValue).Apply(act);
            else
              act(v);
        }
      }
    }

    public static bool EqualElements(ArrayValue arr1, ArrayValue arr2) {
      if (arr1 == arr2)
        return true;
      if (arr1 == null || arr2 == null)
        return false;
      if (arr1.Rows != arr2.Rows || arr1.Cols != arr2.Cols)
        return false;
      for (int c = 0; c < arr1.Cols; c++)
        for (int r = 0; r < arr1.Rows; r++) {
          Value v1 = arr1[c, r], v2 = arr2[c, r];
          if (v1 != v2)
            if (v1 == null || v2 == null)
              return false;
            else if (!(v1.Equals(v2)))
              return false;
        }
      return true;
    }

    public override int GetHashCode() {
      int result = Rows * 37 + Cols;
      for (int i = 0; i < Rows && i < Cols; i++)
        result = result * 37 + this[i, i].GetHashCode();
      return result;
    }

    public override String ToString() {
      StringBuilder sb = new StringBuilder();
      for (int r = 0; r < Rows; r++) {
        for (int c = 0; c < Cols; c++) {
          Value v = this[c, r];
          sb.Append(v == null ? "[none]" : v.ToString());
          if (c < Cols - 1)
            sb.Append("\t");
        }
        if (r < Rows - 1)
          sb.Append("\n");
      }
      return sb.ToString();
    }
  }

  // ----------------------------------------------------------------
  // An ArrayView is a rectangular view of a sheet; the
  // result of evaluating a CellArea expression.  Accessing an 
  // element of an ArrayView may cause a cell to be evaluated.

  public class ArrayView : ArrayValue, IEquatable<ArrayView> {
    public readonly CellAddr ulCa, lrCa;  // ulCa to the left and above lrCa
    public readonly Sheet sheet;          // non-null
    private readonly int cols, rows;

    private ArrayView(CellAddr ulCa, CellAddr lrCa, Sheet sheet) {
      this.sheet = sheet;
      this.ulCa = ulCa;
      this.lrCa = lrCa;
      this.cols = lrCa.col - ulCa.col + 1;
      this.rows = lrCa.row - ulCa.row + 1;
    }

    public override int CommunicationCost {
        get { throw new System.NotImplementedException(); }
    }

    public static ArrayView Make(CellAddr ulCa, CellAddr lrCa, Sheet sheet) {
      CellAddr.NormalizeArea(ulCa, lrCa, out ulCa, out lrCa);
      return new ArrayView(ulCa, lrCa, sheet);
    }

    public override int Cols { get { return cols; } }

    public override int Rows { get { return rows; } }

    // Evaluate and get value at offset [col, row], 0-based
    public override Value this[int col, int row] {
      get {
        if (0 <= col && col < Cols && 0 <= row && row < Rows) {
          int c = ulCa.col + col, r = ulCa.row + row;
          Cell cell = sheet[c, r];
          if (cell != null)
            return cell.Eval(sheet, c, r);
          else
            return null;
        } else
          return ErrorValue.naError;
      }
    }

    public override Value View(CellAddr ulCa, CellAddr lrCa) {
      return ArrayView.Make(ulCa.Offset(this.ulCa), lrCa.Offset(this.ulCa), sheet);
    }

    public override Value Slice(CellAddr ulCa, CellAddr lrCa) {
      return new ArrayView(ulCa.Offset(this.ulCa), lrCa.Offset(this.ulCa), sheet);
    }

    public void Apply(Action<FullCellAddr> act) {
      int col0 = ulCa.col, row0 = ulCa.row;
      for (int c = 0; c < cols; c++)
        for (int r = 0; r < rows; r++)
          act(new FullCellAddr(sheet, col0 + c, row0 + r));
    }

    public override bool Equals(Value v) {
      return v is ArrayView && Equals(v as ArrayView)
             || EqualElements(this, v as ArrayValue);
    }

    // Used at codegen time for numbering and caching CGNormalCellArea expressions 
    // in sheet-defined functions.  NB: Must not compare element values.
    public bool Equals(ArrayView other) {
      return sheet == other.sheet && ulCa.Equals(other.ulCa) && lrCa.Equals(other.lrCa);
    }

    public override bool Equals(Object o) {
      return o is ArrayView && Equals((ArrayView)o);
    }

    public override int GetHashCode() {
      return (ulCa.GetHashCode() * 29 + lrCa.GetHashCode()) * 37 + sheet.GetHashCode();
    }
  }

  // ----------------------------------------------------------------
  // An ArrayExplicit is a (view of) a concrete array of Values.

  public class ArrayExplicit : ArrayValue {
    public readonly CellAddr ulCa, lrCa;      // ulCa to the left and above lrCa
    public readonly Value[,] values;          // non-null
    private readonly int cols, rows;

    public ArrayExplicit(Value[,] values)
      : this(new CellAddr(0, 0), new CellAddr(values.GetLength(0) - 1, values.GetLength(1) - 1), values) { }

    public ArrayExplicit(CellAddr ulCa, CellAddr lrCa, Value[,] values) {
      this.ulCa = ulCa;
      this.lrCa = lrCa;
      this.values = values;
      this.cols = lrCa.col - ulCa.col + 1;
      this.rows = lrCa.row - ulCa.row + 1;
    }

    public override Value View(CellAddr ulCa, CellAddr lrCa) {
      return new ArrayExplicit(ulCa.Offset(this.ulCa), lrCa.Offset(this.ulCa), values);
    }

    public override Value Slice(CellAddr ulCa, CellAddr lrCa) {
      return View(ulCa, lrCa);
    }

    public override int Cols { get { return cols; } }

    public override int Rows { get { return rows; } }

    // Evaluate and get value at offset [col, row], 0-based
    public override Value this[int col, int row] {
      get {
        if (0 <= col && col < Cols && 0 <= row && row < Rows) {
          int c = ulCa.col + col, r = ulCa.row + row;
          return values[c, r];
        } else
          return ErrorValue.naError;
      }
    }

    public override bool Equals(Value v) {
      return EqualElements(this, v as ArrayValue);
    }
  }

  // ----------------------------------------------------------------
  // An ArrayMatrix is a rectangular array of doubles, typically the
  // result of evaluating a linear algebra operation, or of unboxing 
  // all elements of an ArrayValue.

  public class ArrayDouble : ArrayValue {
    // Note: rows and columns are swapped relative to ArrayExplicit,
    // so indexing should go matrix[row, col] -- as in .NET 2D arrays
    public readonly double[,] matrix;

    public new static readonly Type type = typeof(ArrayDouble);
    public static readonly MethodInfo
      makeMethod = type.GetMethod("Make");

    public ArrayDouble(int cols, int rows) {
      this.matrix = new double[rows, cols];
    }

    public ArrayDouble(double[,] matrix) {
      this.matrix = matrix;
    }

    public override int Cols {
      get { return matrix.GetLength(1); }
    }

    public override int Rows {
      get { return matrix.GetLength(0); }
    }

    public override Value this[int col, int row] {
      get { return NumberValue.Make(matrix[row, col]); }
    }

    public override Value View(CellAddr ulCa, CellAddr lrCa) {
      int cols = Cols, rows = Rows, col0 = ulCa.col, row0 = ulCa.row;
      Value[,] vals = new Value[cols, rows];
      for (int c = 0; c < cols; c++)
        for (int r = 0; r < rows; r++)
          vals[c, r] = NumberValue.Make(matrix[row0 + r, col0 + c]);
      return new ArrayExplicit(vals);
    }

    public override Value Slice(CellAddr ulCa, CellAddr lrCa) {
      return View(ulCa, lrCa);
    }

    // Fast, but external array modification could undermine semantics 
    public override double[,] ToDoubleArray2DFast() {
      return matrix;
    }

    public static Value Make(Value v) {
      if (v is ArrayDouble)
        return v;
      else if (v is ArrayValue) {
        ArrayValue arr = v as ArrayValue;
        int cols = arr.Cols, rows = arr.Rows;
        ArrayDouble result = new ArrayDouble(cols, rows);
        for (int r = 0; r < rows; r++)
          for (int c = 0; c < cols; c++)
            result.matrix[r, c] = Value.ToDoubleOrNan(arr[c, r]);
        return result;
      } else
        return ErrorValue.argTypeError;
    }

    public override bool Equals(Value v) {
      return EqualElements(this, v as ArrayValue);
    }
  }

  // ----------------------------------------------------------------
  // A FunctionValue is a partially applied sheet-defined function.
  // Depends on Funcalc.SdfInfo and so probably should be elsewhere.

  public class FunctionValue : Value {
    public readonly SdfInfo sdfInfo;
    public readonly Value[] args;  // Invariant: args.Length==sdfInfo.Arity
    private readonly int arity;    // Invariant: arity==number of #NA in args
    private readonly Delegate mergeAndCall;

    // Used by code generation for CGApply and CGClosure
    public new static readonly Type type = typeof(FunctionValue);
    public static readonly MethodInfo
      makeMethod = type.GetMethod("Make"),
      furtherApplyMethod = type.GetMethod("FurtherApply");
    private static readonly FieldInfo
      sdfInfoField = type.GetField("sdfInfo"),
      argsField = type.GetField("args");
    private static readonly Type[] mergeAndCallDelegateType
      = { typeof(Func<FunctionValue,Value>),
          typeof(Func<FunctionValue,Value,Value>),
          typeof(Func<FunctionValue,Value,Value,Value>),
          typeof(Func<FunctionValue,Value,Value,Value,Value>),
          typeof(Func<FunctionValue,Value,Value,Value,Value,Value>),
          typeof(Func<FunctionValue,Value,Value,Value,Value,Value,Value>),
          typeof(Func<FunctionValue,Value,Value,Value,Value,Value,Value,Value>),
          typeof(Func<FunctionValue,Value,Value,Value,Value,Value,Value,Value,Value>),
          typeof(Func<FunctionValue,Value,Value,Value,Value,Value,Value,Value,Value,Value>),
          typeof(Func<FunctionValue,Value,Value,Value,Value,Value,Value,Value,Value,Value,Value>),
          typeof(Func<FunctionValue,Value,Value,Value,Value,Value,Value,Value,Value,Value,Value,Value>) };
    private static readonly IDictionary<int, Delegate> mergeDelegateCache
      = new Dictionary<int, Delegate>();
    private static readonly Type[][] mergerArgTypes;

    public static readonly MethodInfo[] callMethods
      = { type.GetMethod("Call0"), 
          type.GetMethod("Call1"), 
          type.GetMethod("Call2"), 
          type.GetMethod("Call3"), 
          type.GetMethod("Call4"),
          type.GetMethod("Call5"),
          type.GetMethod("Call6"),
          type.GetMethod("Call7"),
          type.GetMethod("Call8"),
          type.GetMethod("Call9") };

    static FunctionValue() {
      mergerArgTypes = new Type[mergeAndCallDelegateType.Length][];
      for (int a=0; a<mergeAndCallDelegateType.Length; a++) {
        Type[] argTypes = mergerArgTypes[a] = new Type[a+1];
        argTypes[0] = FunctionValue.type;
        for (int i = 0; i < a; i++)
          argTypes[i + 1] = Value.type;
      }
    }

    public override int CommunicationCost {
        get { throw new System.NotImplementedException(); }
    }

    public FunctionValue(SdfInfo sdfInfo, Value[] args) {
      this.sdfInfo = sdfInfo;
      // A null or empty args array is equivalent to an array of all NA
      if (args == null || args.Length == 0) {
        args = new Value[sdfInfo.arity];
        for (int i = 0; i < args.Length; i++)
          args[i] = ErrorValue.naError;
      }
      // Requirement: There will be no further writes to the args array
      this.args = args;
      int k = 0;
      for (int i = 0; i < args.Length; i++)
        if (args[i] == ErrorValue.naError)
          k++;
      this.arity = k;
      this.mergeAndCall = MakeMergeAndCallMethod();
    }

    public override bool Equals(Object obj) {
      return Equals(obj as FunctionValue);
    }

    public override bool Equals(Value v) {
      return Equals(v as FunctionValue);
    }

    public bool Equals(FunctionValue that) {
      if (that == null
          || this.sdfInfo.index != that.sdfInfo.index
          || this.args.Length != that.args.Length)
        return false;
      for (int i = 0; i < args.Length; i++)
        if (!this.args[i].Equals(that.args[i]))
          return false;
      return true;
    }

    public override int GetHashCode() {
      int result = sdfInfo.index * 37 + args.Length;
      for (int i = 0; i < args.Length; i++)
        result = result * 37 + args[i].GetHashCode();
      return result;
    }

    public override Object ToObject() {
      return (Object)this;
    }

    // This array statically allocated and reusedto avoid allocation
    private static readonly Value[] allArgs = new Value[10]; 

    public Value Apply(Value[] vs) {
      if (arity != vs.Length)
        return ErrorValue.argCountError;
      else {
        MergeArgs(vs, allArgs); 
        return sdfInfo.Apply(allArgs);
      }
    }

    // Replace #NA from args with late arguments in output.
    // There is a special version of this loop in CGSdfCall.PEval too
    private void MergeArgs(Value[] late, Value[] output) {
      int j = 0;
      for (int i = 0; i < args.Length; i++)
        if (args[i] != ErrorValue.naError)
          output[i] = args[i];
        else
          output[i] = late[j++];
    }

    // These methods are called both directly and by generated code/reflection:

    public static FunctionValue Make(int sdfIndex, Value[] vs) {
      return new FunctionValue(SdfManager.GetInfo(sdfIndex), vs);
    }

    public Value FurtherApply(Value[] vs) {
      if (vs.Length == 0)
        return this;
      else if (vs.Length != arity)
        return ErrorValue.argCountError;
      else {
        Value[] newArgs = new Value[args.Length];
        MergeArgs(vs, newArgs);
        return new FunctionValue(sdfInfo, newArgs);
      }
    }

    public Value Call0() {
      return sdfInfo.Apply(args); // Shortcut when no late arguments
    }

    public Value Call1(Value v1) {
      if (arity != 1)
        return ErrorValue.argCountError;
      else {
        var caller = (Func<FunctionValue,Value,Value>)mergeAndCall;
        return caller(this, v1);
      }
    }

    public Value Call2(Value v1, Value v2) {
      if (arity != 2)
        return ErrorValue.argCountError;
      else {
        var caller = (Func<FunctionValue,Value,Value,Value>)mergeAndCall;
        return caller(this, v1, v2);
      }
    }

    public Value Call3(Value v1, Value v2, Value v3) {
      if (arity != 3)
        return ErrorValue.argCountError;
      else {
        var caller = (Func<FunctionValue,Value,Value,Value,Value>)mergeAndCall;
        return caller(this, v1, v2, v3);
      }
    }

    public Value Call4(Value v1, Value v2, Value v3, Value v4) {
      if (arity != 4)
        return ErrorValue.argCountError;
      else {
        var caller = (Func<FunctionValue,Value,Value,Value,Value,Value>)mergeAndCall;
        return caller(this, v1, v2, v3, v4);
      }
    }

    public Value Call5(Value v1, Value v2, Value v3, Value v4, Value v5) {
      if (arity != 5)
        return ErrorValue.argCountError;
      else {
        var caller = (Func<FunctionValue,Value,Value,Value,Value,Value,Value>)mergeAndCall;
        return caller(this, v1, v2, v3, v4, v5);
      }
    }

    public Value Call6(Value v1, Value v2, Value v3, Value v4, Value v5, Value v6) {
      if (arity != 6)
        return ErrorValue.argCountError;
      else {
        var caller = (Func<FunctionValue,Value,Value,Value,Value,Value,Value,Value>)mergeAndCall;
        return caller(this, v1, v2, v3, v4, v5, v6);
      }
    }

    public Value Call7(Value v1, Value v2, Value v3, Value v4, Value v5, Value v6, Value v7) {
      if (arity != 7)
        return ErrorValue.argCountError;
      else {
        var caller = (Func<FunctionValue,Value,Value,Value,Value,Value,Value,Value,Value>)mergeAndCall;
        return caller(this, v1, v2, v3, v4, v5, v6, v7);
      }
    }

    public Value Call8(Value v1, Value v2, Value v3, Value v4, Value v5, Value v6, Value v7, Value v8) {
      if (arity != 8)
        return ErrorValue.argCountError;
      else {
        var caller = (Func<FunctionValue,Value,Value,Value,Value,Value,Value,Value,Value,Value>)mergeAndCall;
        return caller(this, v1, v2, v3, v4, v5, v6, v7, v8);
      }
    }

    public Value Call9(Value v1, Value v2, Value v3, Value v4, Value v5, Value v6, Value v7, Value v8, Value v9) {
      if (arity != 9)
        return ErrorValue.argCountError;
      else {
        var caller = (Func<FunctionValue,Value,Value,Value,Value,Value,Value,Value,Value,Value,Value>)mergeAndCall;
        return caller(this, v1, v2, v3, v4, v5, v6, v7, v8, v9);
      }
    }

    public int Arity { get { return arity; } }

    // Generate a method EntryN(fv,v1,...,vN) to merge early arguments from 
    // fv.args with late arguments v1...vN and call fv.sdfInfo delegate.
    // Assumptions: The call-time fv.args array has the same length as the 
    // generation-time args array, and call-time fv.arity equals generation-time arity. 

    private Delegate MakeMergeAndCallMethod() {
      Delegate result;
      int pattern = NaPattern(args);
      if (!mergeDelegateCache.TryGetValue(pattern, out result)) {
        // Console.WriteLine("Created function value merger pattern {0}", pattern);
        DynamicMethod method = new DynamicMethod("EntryN", Value.type, mergerArgTypes[Arity], true);
        ILGenerator ilg = method.GetILGenerator();
        // Load and cast the SDF delegate to call
        ilg.Emit(OpCodes.Ldsfld, SdfManager.sdfDelegatesField);  // sdfDelegates
        ilg.Emit(OpCodes.Ldarg_0);                               // sdfDelegates, fv 
        ilg.Emit(OpCodes.Ldfld, sdfInfoField);                   // sdfDelegates, fv.sdfInfo
        ilg.Emit(OpCodes.Ldfld, SdfInfo.indexField);             // sdfDelegates, fv.sdfInfo.index
        ilg.Emit(OpCodes.Ldelem_Ref);                            // sdfDelegates[fv.sdfInfo.index] 
        ilg.Emit(OpCodes.Castclass, sdfInfo.MyType);             // sdf delegate of appropriate type
        // Save the early argument array to a local variable
        LocalBuilder argsArray = ilg.DeclareLocal(typeof(Value[]));
        ilg.Emit(OpCodes.Ldarg_0);                               // sdf, fv
        ilg.Emit(OpCodes.Ldfld, argsField);                      // sdf, fv.args
        ilg.Emit(OpCodes.Stloc, argsArray);                      // sdf
        // Do like MergeArgs(new Value[] { v1...vn }, ...) but without array overhead:
        int j = 1; // The first late argument is arg1
        for (int i = 0; i < args.Length; i++) {
          if (args[i] != ErrorValue.naError) { // Load early arguments from fv.args
            ilg.Emit(OpCodes.Ldloc, argsArray);
            ilg.Emit(OpCodes.Ldc_I4, i);
            ilg.Emit(OpCodes.Ldelem_Ref);
          } else // Load late arguments from the EntryN method's parameters
            ilg.Emit(OpCodes.Ldarg, j++);
        }
        ilg.Emit(OpCodes.Call, sdfInfo.MyInvoke);
        ilg.Emit(OpCodes.Ret);
        result = method.CreateDelegate(mergeAndCallDelegateType[arity]);
        mergeDelegateCache[pattern] = result;
      }
      return result;
    }

    // Convert a #NA-pattern to an index for caching purposes; think 
    // of the #NA-pattern as a dyadic number with #NA=1 and non-#NA=2
    private static int NaPattern(Value[] args) {
      int index = 0;
      for (int i = 0; i < args.Length; i++)
        index = 2 * index + (args[i] == ErrorValue.naError ? 1 : 2);
      return index;
    }

    public static String FormatAsCall(String name, params Object[] args) {
      StringBuilder sb = new StringBuilder();
      sb.Append(name).Append("(");
      if (args.Length > 0)
        sb.Append(args[0]);
      for (int i = 1; i < args.Length; i++)
        sb.Append(",").Append(args[i]);
      return sb.Append(")").ToString();
    }

    public override String ToString() { // Don't show args if all are #NA
      return Arity < args.Length ? FormatAsCall(sdfInfo.name, args) : sdfInfo.name;
    }
  }
}